/**
 * Licensed under the Apache License Version 2.0.
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package net.interknit.knitter.cg.model;

import java.util.Collection;
import java.util.Hashtable;
import java.util.Map;

import net.interknit.knitter.cg.util.ReflectionHelper;

/**
 * Starting point Class for scanning Java Classes. A single JavaTypeScanner can be created for scanning one or
 * more Java Classes. The typical usage would be:<br/>
 * <br/>
 *
 * <pre>
 *   JavaTypeScanner scanner = new JavaTypeScanner(Xyz.class);
 *
 *   scanner.scan();
 *
 *   // get typeMap that containsed all scanned JavaTypes
 *   Map<String, JavaType> typeMap = scanner.getJavaTypeMap();
 *
 * </pre>
 *
 * The returned <code>map</code> can be used to iterate over the scanned JavaTypes.
 */
public class JavaTypeScanner
{
	/**
	 * The Java Class to be scanned
	 */
	private final Class<?> clazz;

	private boolean scanComplete;

	/**
	 * Construct JavaTypeScanner with the Class object that will be scanned
	 *
	 * @param clazz
	 *            The Java Class to be scanned
	 */
	public JavaTypeScanner(Class<?> clazz)
	{
		this.clazz = clazz;
		this.scanComplete = false;
	}

	/**
	 * Entry-point method for the code generation logic.
	 * Start the scanning of clazz object specified in JavaTypeScanner's constructor. Upon completion
	 * getJavaTypeMap() can be called on this instance of JavaTypeScanner, which will return a Map of all
	 * JavaType objects created for the given clazz object.
	 *
	 * @return This instance of JavaTypeScanner
	 */
	public JavaTypeScanner scan()
	{
		try
		{
			long start = System.currentTimeMillis();

			JavaTypeMap.begin();

			buildJavaType(JavaType.create(clazz));

			for(int mapSize = JavaTypeMap.size(); ; mapSize = JavaTypeMap.size())
			{
				for(String javaTypeId : JavaTypeMap.keys())
					buildJavaType(JavaTypeMap.get(javaTypeId));

				if(JavaTypeMap.size() == mapSize)
				{
					JavaTypeMap.complete();
					scanComplete = true;

					break;
				}
			}

			long end = System.currentTimeMillis();

			JavaTypeMap.printResult(
					ReflectionHelper.createUniqueId(clazz),
					(end - start));

			return this;
		}
		catch(Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	/**
	 * Builds the given JavaType if not built before. Building will populate various fields of this JavaType
	 * lazily, which can be accessed by the corresponding getter methods later.  If JavaType is already built
	 * before, this method will not rebuild it.
	 *
	 * @param javaType the JavaType that will be built
	 */
	private void buildJavaType(JavaType javaType)
	{
		if(! javaType.isBuilt())
		{
			javaType.buildComponentJavaType();
			javaType.buildNoArrayJavaTypeCache();
			javaType.buildConcreteJavaType();
			javaType.buildGenericJavaTypes();
			javaType.buildJavaMethods();
			javaType.setBuilt(true);
		}
	}

	/**
	 * Confirm that the scan is completed.  If scan() is not completed, throw a RuntimeException.
	 * @throws RuntimeException if assertion fails
	 */
	public void assertScanComplete()
	{
		if(! scanComplete)
			throw new RuntimeException(
				"JavaTypeScanner.scan() is not called yet, or is in progress");
	}

	/**
	 * Return a Collection of scanned JavaType objects. <br/>
	 * <br/>
	 * This method will return an empty Collection if scan() is not called previously on this JavaTypeScanner.
	 *
	 * @return Collection of JavaType objects created by scanning the clazz object.
	 */
	public Collection<JavaType> getJavaTypes()
	{
		return getJavaTypeMap().values();
	}

	/**
	 * Return a map of scanned JavaType objects, where each JavaType object (value) can be accessed by
	 * iterating over the JavaType ID (key) set of the returned map. <br/>
	 * <br/>
	 * This method will return an empty Map if scan() is not called on previously on this JavaTypeScanner.
	 *
	 * @return Map of JavaType ID (key) and JavaType (value) for all JavaType objects created by scanning the
	 *         clazz object.
	 */
	public Map<String, JavaType> getJavaTypeMap()
	{
		return new Hashtable<String, JavaType>(JavaTypeMap.map());
	}
}
